// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Copyright 2011 Paul R. Dixon  paul@edobashira.com
// Based on Standard Weight from OpenFst

#include <fst/float-weight.h>

namespace fst
{
	// max  semiring: max, +, -inf, 0)	
	template <class T>
	class MaxPlusWeightTpl : public FloatWeightTpl<T> 	{
	public:
		using FloatWeightTpl<T>::Value;

		typedef MaxPlusWeightTpl ReverseWeight; 

		MaxPlusWeightTpl() : FloatWeightTpl<T>() { }

		MaxPlusWeightTpl(T f) : FloatWeightTpl<T>(f) { }

		MaxPlusWeightTpl(const MaxPlusWeightTpl<T> &w) : FloatWeightTpl<T>(w) { }

		static const MaxPlusWeightTpl<T> Zero() {
			return MaxPlusWeightTpl<T>(FloatLimits<T>::kNegInfinity);
		}

		static const MaxPlusWeightTpl<T> One() {
			return MaxPlusWeightTpl<T>(0.0F);
		}

		static const string &Type() {
			static const string type = "maxplus" + FloatWeightTpl<T>::GetPrecisionString();
			return type;
		}

		bool Member() const	{
			return Value() == Value() && Value() != FloatLimits<T>::kPosInfinity;
		}

		MaxPlusWeightTpl<T> Quantize(T delta = kDelta) const {
			if (Value() == FloatLimits<T>::kNegInfinity || 
					Value() == FloatLimits<T>::kPosInfinity || Value() != Value())
				return *this;
			else
				return MaxPlusWeightTpl<T>(floor(Value() / delta + 0.5F) * delta);
		}

		MaxPlusWeightTpl<T> Reverse() const { 
			return *this;
		}

		static uint64 Properties() {
			return	 kLeftSemiring | kRightSemiring | kCommutative | kPath | kIdempotent;
		}
	};
	

	template <class T>
	inline MaxPlusWeightTpl<T> Plus(const MaxPlusWeightTpl<T> &w1,
															 const MaxPlusWeightTpl<T> &w2) {
		return w1.Value() > w2.Value() ? w1 : w2;
	}

	inline MaxPlusWeightTpl<float> Plus(const MaxPlusWeightTpl<float> &w1,
																	 const MaxPlusWeightTpl<float> &w2) {
		return Plus<float>(w1, w2);
	}

	inline MaxPlusWeightTpl<double> Plus(const MaxPlusWeightTpl<double> &w1,
																		const MaxPlusWeightTpl<double> &w2) {
		return Plus<double>(w1, w2);
	}

	template <class T>
	inline MaxPlusWeightTpl<T> Times(const MaxPlusWeightTpl<T> &w1,
																const MaxPlusWeightTpl<T> &w2)	{
		T f1 = w1.Value(), f2 = w2.Value();
		if (f1 == FloatLimits<T>::kNegInfinity)
		  return w1;
		else if (f2 == FloatLimits<T>::kNegInfinity)
		  return w2;
		else
		  return MaxPlusWeightTpl<T>(f1 + f2);
	}

	inline MaxPlusWeightTpl<float> Times(const MaxPlusWeightTpl<float> &w1,
																		const MaxPlusWeightTpl<float> &w2)	{
			return Times<float>(w1, w2);
	}

	inline MaxPlusWeightTpl<double> Times(const MaxPlusWeightTpl<double> &w1,
																		 const MaxPlusWeightTpl<double> &w2)	{
			return Times<double>(w1, w2);
	}

	template <class T>
	inline MaxPlusWeightTpl<T> Divide(const MaxPlusWeightTpl<T> &w1,
																 const MaxPlusWeightTpl<T> &w2,
																 DivideType typ = DIVIDE_ANY)	{
			T f1 = w1.Value();
			T f2 = w2.Value();
			if (f2 == FloatLimits<T>::kNegInfinity)
				return MaxPlusWeightTpl<T>::Zero();
			else if (f1 == FloatLimits<T>::kNegInfinity)
				return FloatLimits<T>::kNegInfinity;
			else
				return MaxPlusWeightTpl<T>(f1/f2);
	}

	inline MaxPlusWeightTpl<float> Divide(const MaxPlusWeightTpl<float> &w1,
																		 const MaxPlusWeightTpl<float> &w2,
																		 DivideType typ = DIVIDE_ANY)	{
			return Divide<float>(w1, w2, typ);
	}

	inline MaxPlusWeightTpl<double> Divide(const MaxPlusWeightTpl<double> &w1,
																			const MaxPlusWeightTpl<double> &w2,
																			DivideType typ = DIVIDE_ANY) {
			return Divide<double>(w1, w2, typ);
	}	

	typedef MaxPlusWeightTpl<float> MaxPlusWeight;
	typedef MaxPlusWeightTpl<double> MaxPlusWeightD;
}
